package com.gzc.just.play.last.war.sceneserver.sight;

import com.gzc.just.play.last.war.sceneserver.gameobject.base.GameObjectCamp;
import com.gzc.just.play.last.war.sceneserver.gameobject.base.GameObjectType;
import com.gzc.just.play.last.war.sceneserver.gameobject.base.Position;
import com.gzc.just.play.last.war.sceneserver.gameobject.unit.GameUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

/**
 * SightManager类的单元测试
 * 
 * @author AI Assistant
 * @version 1.0.0
 * @since 2025-11-25
 */
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@DisplayName("视野管理器测试")
class SightManagerTest {

    @Mock
    private GameUnit mockObserver;
    
    @Mock
    private GameUnit mockFriendlyUnit;
    
    @Mock
    private GameUnit mockEnemyUnit;
    
    @Mock
    private GameUnit mockNeutralUnit;
    
    @Mock
    private SightListener mockListener;
    
    private SightManager sightManager;
    private Position observerPosition;
    private Position nearbyPosition;
    private Position farawayPosition;
    private Position veryFarPosition;
    
    @BeforeEach
    void setUp() {
        sightManager = new SightManager(mockObserver);
        
        // 设置位置
        observerPosition = new Position(100.0, 0.0, 100.0);
        nearbyPosition = new Position(150.0, 0.0, 150.0); // 距离约70.7
        farawayPosition = new Position(300.0, 0.0, 300.0);  // 距离约282.8
        veryFarPosition = new Position(500.0, 0.0, 500.0); // 距离约565.7
        
        // 设置观察者属性
        when(mockObserver.getPosition()).thenReturn(observerPosition);
        when(mockObserver.getObjectType()).thenReturn(GameObjectType.HERO);
        when(mockObserver.getCamp()).thenReturn(GameObjectCamp.TEAM_1);
        
        // 设置友方单位属性
        when(mockFriendlyUnit.getPosition()).thenReturn(nearbyPosition);
        when(mockFriendlyUnit.getObjectId()).thenReturn(1001L);
        when(mockFriendlyUnit.isActive()).thenReturn(true);
        when(mockFriendlyUnit.getCamp()).thenReturn(GameObjectCamp.TEAM_1);
        
        // 设置敌方单位属性
        when(mockEnemyUnit.getPosition()).thenReturn(nearbyPosition);
        when(mockEnemyUnit.getObjectId()).thenReturn(1002L);
        when(mockEnemyUnit.isActive()).thenReturn(true);
        when(mockEnemyUnit.getCamp()).thenReturn(GameObjectCamp.TEAM_2);
        
        // 设置中立单位属性
        when(mockNeutralUnit.getPosition()).thenReturn(farawayPosition);
        when(mockNeutralUnit.getObjectId()).thenReturn(1003L);
        when(mockNeutralUnit.isActive()).thenReturn(true);
        when(mockNeutralUnit.getCamp()).thenReturn(GameObjectCamp.NEUTRAL);
    }
    
    @Test
    @DisplayName("测试默认视野范围")
    void testDefaultSightRange() {
        // 英雄的默认视野范围
        float range = sightManager.getSightRange();
        assertEquals(200.0f, range, 0.001f);
        
        // 验证可见性计算
        assertTrue(sightManager.isInSight(mockFriendlyUnit)); // 在范围内
        assertFalse(sightManager.isInSight(mockNeutralUnit)); // 超出范围
    }
    
    @Test
    @DisplayName("测试不同对象类型的视野范围")
    void testDifferentObjectTypeSightRanges() {
        // 士兵的视野范围
        when(mockObserver.getObjectType()).thenReturn(GameObjectType.SOLDIER);
        sightManager = new SightManager(mockObserver);
        assertEquals(120.0f, sightManager.getSightRange(), 0.001f);
        
        // 建筑的视野范围
        when(mockObserver.getObjectType()).thenReturn(GameObjectType.BUILDING);
        sightManager = new SightManager(mockObserver);
        assertEquals(150.0f, sightManager.getSightRange(), 0.001f);
        
        // 默认其他类型的视野范围
        when(mockObserver.getObjectType()).thenReturn(GameObjectType.MONSTER);
        sightManager = new SightManager(mockObserver);
        assertEquals(200.0f, sightManager.getSightRange(), 0.001f);
    }
    
    @Test
    @DisplayName("测试自定义视野范围")
    void testCustomSightRange() {
        // 设置自定义视野范围
        sightManager.setSightRange(300.0f);
        assertEquals(300.0f, sightManager.getSightRange(), 0.001f);
        
        // 验证可见性计算更新
        assertTrue(sightManager.isInSight(mockNeutralUnit)); // 现在在范围内
        
        // 恢复默认视野范围
        sightManager.setSightRange(-1);
        assertEquals(200.0f, sightManager.getSightRange(), 0.001f);
        assertFalse(sightManager.isInSight(mockNeutralUnit)); // 再次超出范围
    }
    
    @Test
    @DisplayName("测试视野内检查")
    void testInSightCheck() {
        // null单位不在视野内
        assertFalse(sightManager.isInSight(null));
        
        // 观察者自身不在视野内
        assertFalse(sightManager.isInSight(mockObserver));
        
        // 非激活单位不在视野内
        when(mockFriendlyUnit.isActive()).thenReturn(false);
        assertFalse(sightManager.isInSight(mockFriendlyUnit));
        
        // 无位置单位不在视野内
        when(mockFriendlyUnit.getPosition()).thenReturn(null);
        assertFalse(sightManager.isInSight(mockFriendlyUnit));
        
        // 恢复状态
        when(mockFriendlyUnit.isActive()).thenReturn(true);
        when(mockFriendlyUnit.getPosition()).thenReturn(nearbyPosition);
        assertTrue(sightManager.isInSight(mockFriendlyUnit));
    }
    
    @Test
    @DisplayName("测试获取可见单位")
    void testGetVisibleUnits() {
        List<GameUnit> allUnits = Arrays.asList(
            mockFriendlyUnit, mockEnemyUnit, mockNeutralUnit
        );
        
        List<GameUnit> visibleUnits = sightManager.getVisibleUnits(allUnits);
        
        // 只有附近单位可见
        assertEquals(2, visibleUnits.size());
        assertTrue(visibleUnits.contains(mockFriendlyUnit));
        assertTrue(visibleUnits.contains(mockEnemyUnit));
        assertFalse(visibleUnits.contains(mockNeutralUnit));
        
        // 扩大视野范围
        sightManager.setSightRange(300.0f);
        visibleUnits = sightManager.getVisibleUnits(allUnits);
        
        // 所有单位都可见
        assertEquals(3, visibleUnits.size());
        assertTrue(visibleUnits.contains(mockNeutralUnit));
    }
    
    @Test
    @DisplayName("测试单位进入/离开视野")
    void testUnitEnterLeaveSight() {
        // 添加监听器
        sightManager.addListener(mockListener);
        
        // 单位进入视野
        sightManager.onUnitVisible(mockFriendlyUnit);
        
        // 验证可见单位列表
        assertEquals(1, sightManager.getVisibleUnitCount());
        assertTrue(sightManager.isUnitVisible(mockFriendlyUnit.getObjectId()));
        
        // 验证事件触发
        verify(mockListener, times(1)).onUnitEnteredSight(mockObserver, mockFriendlyUnit);
        
        // 单位离开视野
        sightManager.onUnitOutOfSight(mockFriendlyUnit);
        
        // 验证可见单位列表
        assertEquals(0, sightManager.getVisibleUnitCount());
        assertFalse(sightManager.isUnitVisible(mockFriendlyUnit.getObjectId()));
        
        // 验证事件触发
        verify(mockListener, times(1)).onUnitLeftSight(mockObserver, mockFriendlyUnit);
    }
    
    @Test
    @DisplayName("测试视野更新")
    void testSightUpdate() {
        List<GameUnit> allUnits = Arrays.asList(
            mockFriendlyUnit, mockEnemyUnit, mockNeutralUnit
        );
        
        // 更新视野
        sightManager.updateSight(allUnits);
        
        // 验证可见单位
        assertEquals(2, sightManager.getVisibleUnitCount());
        assertTrue(sightManager.isUnitVisible(mockFriendlyUnit.getObjectId()));
        assertTrue(sightManager.isUnitVisible(mockEnemyUnit.getObjectId()));
        assertFalse(sightManager.isUnitVisible(mockNeutralUnit.getObjectId()));
        
        // 扩大视野范围并再次更新
        sightManager.setSightRange(300.0f);
        sightManager.updateSight(allUnits);
        
        // 验证所有单位都可见
        assertEquals(3, sightManager.getVisibleUnitCount());
        assertTrue(sightManager.isUnitVisible(mockNeutralUnit.getObjectId()));
    }
    
    @Test
    @DisplayName("测试按距离排序的可见单位")
    void testGetVisibleUnitsSorted() {
        // 设置不同距离的单位
        when(mockFriendlyUnit.getPosition()).thenReturn(new Position(110.0, 0.0, 110.0));  // 距离约14.1
        when(mockEnemyUnit.getPosition()).thenReturn(new Position(130.0, 0.0, 130.0));    // 距离约42.4
        when(mockNeutralUnit.getPosition()).thenReturn(new Position(150.0, 0.0, 150.0));  // 距离约70.7
        
        List<GameUnit> allUnits = Arrays.asList(
            mockEnemyUnit, mockNeutralUnit, mockFriendlyUnit
        );
        
        // 更新视野
        sightManager.updateSight(allUnits);
        
        // 获取按距离排序的可见单位
        List<GameUnit> sortedUnits = sightManager.getVisibleUnitsSorted();
        
        // 验证排序顺序（从近到远）
        assertEquals(mockFriendlyUnit, sortedUnits.get(0)); // 最近的
        assertEquals(mockEnemyUnit, sortedUnits.get(1));
        assertEquals(mockNeutralUnit, sortedUnits.get(2)); // 最远的
    }
    
    @Test
    @DisplayName("测试获取最近的可见敌人")
    void testGetNearestVisibleEnemy() {
        // 设置不同距离的敌方单位
        GameUnit farEnemy = mock(GameUnit.class);
        when(farEnemy.getObjectId()).thenReturn(2001L);
        when(farEnemy.getPosition()).thenReturn(new Position(180.0, 0.0, 180.0)); // 距离约113.1
        when(farEnemy.isActive()).thenReturn(true);
        when(farEnemy.getCamp()).thenReturn(GameObjectCamp.TEAM_2);
        
        List<GameUnit> allUnits = Arrays.asList(
            mockFriendlyUnit, mockEnemyUnit, mockNeutralUnit, farEnemy
        );
        
        // 更新视野
        sightManager.updateSight(allUnits);
        
        // 获取最近的敌人
        GameUnit nearestEnemy = sightManager.getNearestVisibleEnemy(allUnits);
        
        // mockEnemyUnit应该更近（距离约70.7）
        assertEquals(mockEnemyUnit, nearestEnemy);
    }
    
    @Test
    @DisplayName("测试清除所有可见单位")
    void testClearVisibleUnits() {
        List<GameUnit> allUnits = Arrays.asList(
            mockFriendlyUnit, mockEnemyUnit, mockNeutralUnit
        );
        
        // 更新视野
        sightManager.updateSight(allUnits);
        assertEquals(2, sightManager.getVisibleUnitCount());
        
        // 扩大视野范围
        sightManager.setSightRange(300.0f);
        sightManager.updateSight(allUnits);
        assertEquals(3, sightManager.getVisibleUnitCount());
        
        // 清除所有可见单位
        sightManager.clearVisibleUnits();
        assertEquals(0, sightManager.getVisibleUnitCount());
        assertFalse(sightManager.isUnitVisible(mockFriendlyUnit.getObjectId()));
        assertFalse(sightManager.isUnitVisible(mockEnemyUnit.getObjectId()));
        assertFalse(sightManager.isUnitVisible(mockNeutralUnit.getObjectId()));
    }
    
    @Test
    @DisplayName("测试视野监听器")
    void testSightListeners() {
        // 添加监听器
        sightManager.addListener(mockListener);
        
        // 更新视野（单位进入视野）
        List<GameUnit> allUnits = Arrays.asList(mockFriendlyUnit, mockEnemyUnit);
        sightManager.updateSight(allUnits);
        
        // 验证进入视野事件被触发
        verify(mockListener, atLeastOnce()).onUnitEnteredSight(mockObserver, mockFriendlyUnit);
        verify(mockListener, atLeastOnce()).onUnitEnteredSight(mockObserver, mockEnemyUnit);
        
        // 清除可见单位后，再调用 updateSight 使单位离开视野
        sightManager.updateSight(new ArrayList<>());
        
        // 验证离开视野事件被触发
        verify(mockListener, atLeastOnce()).onUnitLeftSight(mockObserver, mockFriendlyUnit);
        verify(mockListener, atLeastOnce()).onUnitLeftSight(mockObserver, mockEnemyUnit);
        
        // 移除监听器
        sightManager.removeListener(mockListener);
        
        // 再次更新视野，监听器不应被调用
        sightManager.updateSight(allUnits);
        
        // 验证进入视野事件没有增加调用次数
        verify(mockListener, atLeastOnce()).onUnitEnteredSight(mockObserver, mockFriendlyUnit);
    }
    
    @Test
    @DisplayName("测试视野摘要")
    void testSightSummary() {
        // 初始状态
        String summary = sightManager.getSightSummary();
        assertTrue(summary.contains("视野范围"));
        assertTrue(summary.contains("200.0"));
        assertTrue(summary.contains("可见单位"));
        assertTrue(summary.contains("0"));
        
        // 更新视野
        List<GameUnit> allUnits = Arrays.asList(mockFriendlyUnit, mockEnemyUnit);
        sightManager.updateSight(allUnits);
        
        summary = sightManager.getSightSummary();
        assertTrue(summary.contains("2"));
        
        // 自定义视野范围
        sightManager.setSightRange(300.0f);
        summary = sightManager.getSightSummary();
        assertTrue(summary.contains("300.0"));
    }
    
    @Test
    @DisplayName("测试可见单位集合")
    void testVisibleUnitsCollection() {
        List<GameUnit> allUnits = Arrays.asList(
            mockFriendlyUnit, mockEnemyUnit, mockNeutralUnit
        );
        
        // 更新视野
        sightManager.updateSight(allUnits);
        
        // 获取所有可见单位集合
        Collection<GameUnit> visibleUnits = sightManager.getVisibleUnits();
        
        // 验证集合内容
        assertEquals(2, visibleUnits.size());
        assertTrue(visibleUnits.contains(mockFriendlyUnit));
        assertTrue(visibleUnits.contains(mockEnemyUnit));
        assertFalse(visibleUnits.contains(mockNeutralUnit));
        
        // 验证返回的是副本（修改不应影响原始集合）
        try {
            visibleUnits.add(mockNeutralUnit);
            // 不应抛出异常，但原始集合不应被修改
            assertEquals(2, sightManager.getVisibleUnitCount());
        } catch (UnsupportedOperationException e) {
            // 如果返回的是不可变集合，这是预期行为
        }
    }
    
    @Test
    @DisplayName("测试重复可见单位处理")
    void testDuplicateVisibleUnitHandling() {
        // 单位进入视野
        sightManager.onUnitVisible(mockFriendlyUnit);
        assertEquals(1, sightManager.getVisibleUnitCount());
        
        // 同一单位再次进入视野（不应增加计数）
        sightManager.onUnitVisible(mockFriendlyUnit);
        assertEquals(1, sightManager.getVisibleUnitCount());
        
        // 单位离开视野
        sightManager.onUnitOutOfSight(mockFriendlyUnit);
        assertEquals(0, sightManager.getVisibleUnitCount());
        
        // 单位再次离开视野（不应出错）
        sightManager.onUnitOutOfSight(mockFriendlyUnit);
        assertEquals(0, sightManager.getVisibleUnitCount());
    }
    
    @Test
    @DisplayName("测试监听器管理边界情况")
    void testListenerManagementEdgeCases() {
        // 添加null监听器
        assertDoesNotThrow(() -> sightManager.addListener(null));
        
        // 移除null监听器
        assertDoesNotThrow(() -> sightManager.removeListener(null));
        
        // 移除不存在的监听器
        SightListener nonExistentListener = mock(SightListener.class);
        assertDoesNotThrow(() -> sightManager.removeListener(nonExistentListener));
        
        // 验证没有添加任何监听器
        sightManager.onUnitVisible(mockFriendlyUnit);
        
        // 应该没有监听器被调用
        verifyNoInteractions(mockListener);
        verifyNoInteractions(nonExistentListener);
    }
    
    @Test
    @DisplayName("测试零距离单位")
    void testZeroDistanceUnit() {
        // 设置单位在同一位置
        when(mockFriendlyUnit.getPosition()).thenReturn(observerPosition);
        
        // 单位应在视野内
        assertTrue(sightManager.isInSight(mockFriendlyUnit));
        
        // 更新视野
        List<GameUnit> allUnits = Arrays.asList(mockFriendlyUnit);
        sightManager.updateSight(allUnits);
        
        // 验证单位可见
        assertEquals(1, sightManager.getVisibleUnitCount());
        assertTrue(sightManager.isUnitVisible(mockFriendlyUnit.getObjectId()));
    }
    
    @Test
    @DisplayName("测试可见单位时间记录")
    void testVisibleUnitTimeTracking() {
        // 单位进入视野
        sightManager.onUnitVisible(mockFriendlyUnit);
        
        // 短暂等待
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        // 同一单位再次进入视野（应更新时间）
        sightManager.onUnitVisible(mockFriendlyUnit);
        
        // 验证单位仍在视野内
        assertTrue(sightManager.isUnitVisible(mockFriendlyUnit.getObjectId()));
    }
}